/* http server and subdomain mux */
package main

import (
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"regexp"
	hash "hash/adler32"
	"code.google.com/p/feedgraph/config"
	"code.google.com/p/cloud9lib/rest"
	"flag"
	"log"
	"io/ioutil"
	"encoding/json"
	"strconv"
)

var nodeProxies []*httputil.ReverseProxy
var bucketRegExp = regexp.MustCompile("[a-zA-Z0-9:]+/[a-zA-Z0-9:]+/[a-zA-Z0-9:]+")

func proxyGet(req *http.Request, proxy *httputil.ReverseProxy, ch chan interface{}) error {

	transport := proxy.Transport

	if transport == nil {
		transport = http.DefaultTransport
	}

	// config outbound request
	outreq := new(http.Request)
	*outreq = *req // includes shallow copies of maps, but okay
	proxy.Director(outreq)
	outreq.Proto = "HTTP/1.1"
	outreq.ProtoMajor = 1
	outreq.ProtoMinor = 1
	outreq.Close = false

	if clientIp, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
		outreq.Header.Set("X-Forwarded-For", clientIp)
	}

	// send request and await response
	res, err := transport.RoundTrip(outreq)
	if err != nil {
		log.Printf("http: proxy error: %v", err)
		return err
	}

	defer res.Body.Close()

	if res.StatusCode != 200 {
		return nil
	}

	bytes, err := ioutil.ReadAll(res.Body)

	if err != nil {
		return err
	}

	var feed []interface{}

	err = json.Unmarshal(bytes, &feed)

	if err != nil {
		return err
	}

	for _, edge := range feed {
		ch <- edge
	}

	return nil
}

func clusterHandler(w http.ResponseWriter, req *http.Request) {

	w.Header().Add("Access-Control-Allow-Origin", "*")
	w.Header().Add("Access-Control-Allow-Methods", "GET")
	w.Header().Add("Access-Control-Allow-Methods", "PUT")
	w.Header().Add("Access-Control-Allow-Methods", "POST")
	w.Header().Add("Access-Control-Allow-Methods", "HEAD")
	w.Header().Add("Access-Control-Allow-Headers", "Content-Type")

	bucket := bucketRegExp.FindString(req.URL.Path)

	if bucket != "" {
		// if the request looks like a feed, proxy to the cluster node responsible for it
		key := hash.Checksum([]byte(bucket))

		i := key % uint32(len(nodeProxies))

		nodeProxies[i].ServeHTTP(w, req)

		return
	}

	// no matching node, so broadcast request to all nodes

	ch := make(chan interface{})

	go func () {
		for _, proxy := range nodeProxies {

			err := proxyGet(req, proxy, ch) 

			if err != nil {
				close(ch)
				w.WriteHeader(500)
				log.Println(err)
				return
			}
		}

		close(ch)
	} ()

	var feed []interface{}

	max, err := strconv.Atoi(req.FormValue("max"))

	if err != nil || max == 0 {
		max = 100
	}

	for edge := range ch {

		if len(feed) >= max {
			break
		}

		feed = append(feed, edge)
	}

	rest.WriteObject(w, req, feed)
}

func main () {

	master := flag.String("master", "", "IP:port of graphd instance with configuration info")

	flag.Parse()

	if *master != "" {
		log.Println("waiting for masterd...")
		config.WaitForMaster(*master)
		log.Println("masterd seems ready")
		config.LoadFromMaster(*master)
		log.Println("config downloaded; configuring httpd...")
	}
	
	for _, redirect := range config.Local.HTTPConfig.Redirects {

		log.Println(redirect.Pattern, " -> ", redirect.Target)
		http.Handle(redirect.Pattern, http.RedirectHandler(redirect.Target, 302))
	}

	for _, proxy := range config.Local.HTTPConfig.Proxies {

		target, err := url.Parse(proxy.Target)

		if err != nil {
			panic(err)
		}

		log.Println(proxy.Pattern, " -> ", proxy.Target)
		http.Handle(proxy.Pattern, httputil.NewSingleHostReverseProxy(target))
	}

	for _, server := range config.Local.HTTPConfig.FileServers {

		log.Println(server.Pattern, " -> ", server.Path)
		http.Handle(server.Pattern, http.FileServer(http.Dir(server.Path)))
	}

	for _, node := range config.Local.ClusterConfig.Nodes {

		target, err := url.Parse("http://"+node.Target+"/")

		if err != nil {
			panic(err)
		}

		log.Println("httpd aware of cluster node", node.Target)

		nodeProxies = append(nodeProxies, httputil.NewSingleHostReverseProxy(target))
	}

	http.HandleFunc(config.Local.HTTPConfig.APIPattern, clusterHandler)

	log.Println("httpd listening...")

	err := http.ListenAndServe(":80", nil)

	if err != nil {
		panic(err)
	}
}
